using System.Collections.Generic;
using UnityEngine;

public class FlightSimulator : Singleton<FlightSimulator>
{
	public enum SimulationEndState
	{
		HitTable = 0,
		HitNet = 1,
		OutOfBounds = 2,
		MaxIterationsReached = 3
	}

	public struct SequenceSimulationParams
	{
		public FlightParams startParams;

		public PhysicsBall ball;

		public BallFlight flightToSymulateWith;

		public float deltaTime;

		public bool record;

		public int maxHops;

		public int maxIterations;

		public static SequenceSimulationParams DefaultRecordFlightParams(FlightParams startParams, BallFlight flightToSymulateWith)
		{
			SequenceSimulationParams result = default(SequenceSimulationParams);
			result.startParams = startParams;
			result.flightToSymulateWith = flightToSymulateWith;
			result.deltaTime = 0.1f;
			result.record = true;
			result.maxHops = 4;
			result.maxIterations = 250;
			return result;
		}
	}

	public struct HopSimulationParams
	{
		public FlightParams startParams;

		public float deltaTime;

		public PhysicsBall ball;

		public BallFlight flight;

		public CatmullRomSplineBallFlight flightRecording;

		public int maxIterations;
	}

	public struct HopSimulationResult
	{
		public HopSimulationParams sim;

		public SimulationEndState endState;

		public FlightParams endParams;

		public float duration;

		public HitObjectTag hitObjectTag;
	}

	public class EndConditionsList
	{
		public class EndCondition
		{
			public SimulationEndState endState;

			public float normTimeWhenHappened;
		}

		protected List<EndCondition> endStates = new List<EndCondition>();

		protected int endStatesCount;

		public void Add(SimulationEndState endState, float normTimeWhenHappened)
		{
			endStatesCount++;
			if (endStatesCount > endStates.Count)
			{
				endStates.Add(new EndCondition());
			}
			EndCondition endCondition = endStates[endStatesCount - 1];
			endCondition.endState = endState;
			endCondition.normTimeWhenHappened = normTimeWhenHappened;
		}

		public void Clear()
		{
			endStatesCount = 0;
		}

		public EndCondition GetFirstEndCondition()
		{
			EndCondition endCondition = null;
			for (int i = 0; i < endStatesCount; i++)
			{
				EndCondition endCondition2 = endStates[i];
				if (endCondition == null)
				{
					endCondition = endCondition2;
				}
				else if (endCondition.normTimeWhenHappened > endCondition2.normTimeWhenHappened || (endCondition.normTimeWhenHappened == endCondition2.normTimeWhenHappened && endCondition.endState > endCondition2.endState))
				{
					endCondition = endCondition2;
				}
			}
			return endCondition;
		}
	}

	private EndConditionsList endConditionsList = new EndConditionsList();

	private HopSimulationResult SimulateHop(HopSimulationParams sim)
	{
		HopSimulationResult result = default(HopSimulationResult);
		result.hitObjectTag = HitObjectTag.Floor;
		result.sim = sim;
		int num = 0;
		BallFlight flight = sim.flight;
		CatmullRomSplineBallFlight flightRecording = sim.flightRecording;
		flight.SetInitialConditions(sim.startParams, sim.ball);
		flight.Reset();
		if (flightRecording != null)
		{
			flightRecording.AddControlPoint(sim.startParams, 0f);
			flightRecording.SetIsDeltaTimeBetweenControllPointsConstant(true);
		}
		FlightParams p = flight.FlightParams();
		float num2 = Table.tabletopy + flight.radius;
		FlightParams flightParams;
		FlightParams flightParams2;
		EndConditionsList.EndCondition firstEndCondition;
		while (true)
		{
			flightParams = flight.FlightParams();
			flight.Update(sim.deltaTime);
			flightParams2 = flight.FlightParams();
			endConditionsList.Clear();
			bool flag = (flightParams.position.z != 0f && Mathf.Sign(flightParams.position.z) != Mathf.Sign(flightParams2.position.z)) || flightParams2.position.z == 0f;
			bool flag2 = false;
			float num3 = 0f;
			if (flag)
			{
				num3 = Mathf.Abs(flightParams.position.z) / Mathf.Abs(flightParams2.position.z - flightParams.position.z);
				num3 = MathEx.CatmullRomInverseLerp(p.position.z, flightParams.position.z, flightParams2.position.z, flightParams2.position.z, 0f, num3);
				if (Vector3Ex.CatmullRomLerp(p.position, flightParams.position, flightParams2.position, flightParams2.position, num3).y - flight.radius < Table.tabletopy + Table.netHeight)
				{
					endConditionsList.Add(SimulationEndState.HitNet, num3);
				}
			}
			bool flag3 = flightParams.position.y >= num2 && flightParams2.position.y <= num2;
			bool flag4 = false;
			float num4 = 0f;
			if (flag3)
			{
				num4 = Mathf.Abs(num2 - flightParams.position.y) / Mathf.Abs(flightParams2.position.y - flightParams.position.y);
				num4 = MathEx.CatmullRomInverseLerp(p.position.y, flightParams.position.y, flightParams2.position.y, flightParams2.position.y, num2, num4);
				Vector3 pos = Vector3Ex.CatmullRomLerp(p.position, flightParams.position, flightParams2.position, flightParams2.position, num4);
				if (Table.IsPosOnTable(pos))
				{
					endConditionsList.Add(SimulationEndState.HitTable, num4);
				}
			}
			bool flag5 = flightParams2.position.y <= 0f;
			float num5 = 0f;
			if (flag5)
			{
				num5 = Mathf.Abs(flightParams.position.y) / Mathf.Abs(flightParams2.position.y - flightParams.position.y);
				endConditionsList.Add(SimulationEndState.OutOfBounds, num5);
			}
			num++;
			if (num > sim.maxIterations)
			{
				endConditionsList.Add(SimulationEndState.MaxIterationsReached, 1f);
			}
			firstEndCondition = endConditionsList.GetFirstEndCondition();
			if (firstEndCondition != null)
			{
				break;
			}
			result.duration += sim.deltaTime;
			if (flightRecording != null)
			{
				flightRecording.AddControlPoint(flightParams2, result.duration);
			}
			p = flightParams;
		}
		if (flightRecording != null)
		{
			flightRecording.AddControlPoint(flightParams2, result.duration + sim.deltaTime);
		}
		result.endState = firstEndCondition.endState;
		result.endParams = FlightParams.CatmullRomLerp(p, flightParams, flightParams2, flightParams2, firstEndCondition.normTimeWhenHappened);
		if (firstEndCondition.endState == SimulationEndState.HitTable)
		{
			result.hitObjectTag = HitObjectTag.Table;
			result.endParams.position.y = num2;
		}
		if (firstEndCondition.endState == SimulationEndState.HitNet)
		{
			result.hitObjectTag = HitObjectTag.Net;
			result.endParams.position.z = 0f;
		}
		result.duration += firstEndCondition.normTimeWhenHappened * sim.deltaTime;
		return result;
	}

	public void FillFlightSequence(FlightSequence sequence, SequenceSimulationParams sim)
	{
		HopSimulationParams sim2 = default(HopSimulationParams);
		sim2.ball = sim.ball;
		sim2.deltaTime = sim.deltaTime;
		sim2.flight = sim.flightToSymulateWith;
		sim2.maxIterations = sim.maxIterations;
		FlightParams startParams = sim.startParams;
		sequence.ClearStages();
		FlightPool flightPool = SingletonInit<FlightPool>.instance;
		int num = 0;
		do
		{
			if (sim.record)
			{
				sim2.flightRecording = flightPool.GetFlightFromPool<CatmullRomSplineBallFlight>();
			}
			sim2.startParams = startParams;
			HopSimulationResult hopSimulationResult = SimulateHop(sim2);
			BallFlight flight = ((!sim.record) ? flightPool.GetFlightFromPool(sim.flightToSymulateWith.GetType()) : hopSimulationResult.sim.flightRecording);
			sequence.AddStage(flight, hopSimulationResult.sim.startParams, hopSimulationResult.duration, false, hopSimulationResult.hitObjectTag);
			startParams = hopSimulationResult.endParams;
			if (hopSimulationResult.endState == SimulationEndState.HitNet)
			{
				startParams = CollisionHandlerFactory.GetBallFlightParamsAfterBounce(CollisionHandlerFactory.CollisionType.CollideWithNet, hopSimulationResult.endParams, sim.flightToSymulateWith.mass, sim.flightToSymulateWith.radius);
				sequence.netHitInfo = new FlightSequence.NetHitInfo
				{
					optionalNetHitNormal = startParams.optionalNormal,
					position = startParams.position
				};
			}
			else
			{
				if (hopSimulationResult.endState != 0)
				{
					break;
				}
				startParams = CollisionHandlerFactory.GetBallFlightParamsAfterBounce(CollisionHandlerFactory.CollisionType.CollideWithTable, hopSimulationResult.endParams, sim.flightToSymulateWith.mass, sim.flightToSymulateWith.radius);
			}
			num++;
		}
		while (num < sim.maxHops);
	}
}
